﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Search;
using Lucene.Net.Util;
using System.Collections;
using Lucene.Net.Documents;
using System.Threading;

namespace Lucandra.Net
{
    public class LucandraSearcher : Searcher
    {
        internal LucandraReader reader;
        private LucandraReader[] subReaders;
        private bool closeReader;
        private int[] docStarts;
        private bool fieldSortDoTrackScores;
        private bool fieldSortDoMaxScore;
        

        #region Constructors

        public LucandraSearcher(LucandraReader r)
            : this(r, false)
        {
        }

        public LucandraSearcher(LucandraReader r, bool closeReader)
        {
            this.reader = r;
            this.closeReader = closeReader;

            IList subReadersList = new ArrayList();
            this.GatherSubReaders(subReadersList, r);
            subReaders = (LucandraReader[])new ArrayList(subReadersList).ToArray(typeof(LucandraReader));
            this.docStarts = new int[subReaders.Length];
            int maxDoc = 0;
            for (int i = 0; i < subReaders.Length; i++)
            {
                docStarts[i] = maxDoc;
                maxDoc += subReaders[i].MaxDoc();
            }
        }

        #endregion

        private void CreateSearchContext()
        {
            if (LucandraSearchContext.Current == null)
                LucandraSearchContext.Current = new LucandraSearchContext(this.reader);
        }

        private void DestroySearchContext()
        {
            LucandraSearchContext.Current = null;
        }

        protected internal virtual void GatherSubReaders(IList allSubReaders, LucandraReader r)
        {
            ReaderUtil.GatherSubReaders(allSubReaders, r);
        }

        public virtual LucandraReader GetIndexReader()
        {
            return this.reader;
        }

        public override void Search(Weight weight, Filter filter, Collector results)
        {
            this.CreateSearchContext();

            if (filter == null)
            {
                for (int i = 0; i < subReaders.Length; i++)
                {
                    // search each subreader
                    results.SetNextReader(subReaders[i], docStarts[i]);
                    Scorer scorer = weight.Scorer(subReaders[i], !results.AcceptsDocsOutOfOrder(), true);
                    if (scorer != null)
                    {
                        scorer.Score(results);
                    }
                }
            }
            else
            {
                for (int i = 0; i < subReaders.Length; i++)
                {
                    // search each subreader
                    results.SetNextReader(subReaders[i], docStarts[i]);
                    this.SearchWithFilter(subReaders[i], weight, filter, results);
                }
            }

            this.DestroySearchContext();
        }

        public override void Close()
        {
            if (this.closeReader)
                this.reader.Close();
        }

        public override int DocFreq(Lucene.Net.Index.Term term)
        {
            this.CreateSearchContext();
            //return this.reader.DocFreq(term);
            var context = LucandraSearchContext.Current;
            return context.GetDocumentFrequency(term);
        }

        public override int MaxDoc()
        {
            return this.reader.MaxDoc();
        }

        public override Lucene.Net.Documents.Document Doc(int i)
        {
            return this.reader.Document(i);
        }

        public override Query Rewrite(Query original)
        {
            Query query = original;
            for (Query rewrittenQuery = query.Rewrite(reader); rewrittenQuery != query; rewrittenQuery = query.Rewrite(reader))
            {
                query = rewrittenQuery;
            }
            return query;
        }

        public override Explanation Explain(Weight weight, int doc)
        {
            int n = ReaderUtil.SubIndex(doc, docStarts);
            int deBasedDoc = doc - docStarts[n];

            return weight.Explain(subReaders[n], deBasedDoc);
        }

        public override TopDocs Search(Weight weight, Filter filter, int n)
        {
            this.CreateSearchContext();

            if (n <= 0)
                throw new System.ArgumentException("nDocs must be > 0");

            TopScoreDocCollector collector = TopScoreDocCollector.create(n, !weight.ScoresDocsOutOfOrder());
            this.Search(weight, filter, collector);

            this.DestroySearchContext();

            return collector.TopDocs();
        }

        public override TopFieldDocs Search(Weight weight, Filter filter, int n, Sort sort)
        {
            TopFieldCollector collector = TopFieldCollector.create(sort, n, true, fieldSortDoTrackScores, fieldSortDoMaxScore, !weight.ScoresDocsOutOfOrder());
            this.Search(weight, filter, collector);
            return (TopFieldDocs)collector.TopDocs();
        }

        public override Weight CreateWeight(Query query)
        {
            this.CreateSearchContext();
            return base.CreateWeight(query);
        }

        public override Lucene.Net.Documents.Document Doc(int documentNumber, FieldSelector fieldSelector)
        {
            return this.reader.Document(documentNumber, fieldSelector);
        }

        public Document[] Docs(IEnumerable<int> documentNumbers)
        {
            return this.reader.Documents(documentNumbers);
        }

        public virtual void SetDefaultFieldSortScoring(bool doTrackScores, bool doMaxScore)
        {
            this.fieldSortDoTrackScores = doTrackScores;
            this.fieldSortDoMaxScore = doMaxScore;
        }

        private void SearchWithFilter(LucandraReader reader, Weight weight, Filter filter, Collector collector)
        {
            System.Diagnostics.Debug.Assert(filter != null);

            Scorer scorer = weight.Scorer(reader, true, false);
            if (scorer == null)
            {
                return;
            }

            int docID = scorer.DocID();
            System.Diagnostics.Debug.Assert(docID == -1 || docID == DocIdSetIterator.NO_MORE_DOCS);

            // CHECKME: use ConjunctionScorer here?
            DocIdSet filterDocIdSet = filter.GetDocIdSet(reader);
            if (filterDocIdSet == null)
            {
                // this means the filter does not accept any documents.
                return;
            }

            DocIdSetIterator filterIter = filterDocIdSet.Iterator();
            if (filterIter == null)
            {
                // this means the filter does not accept any documents.
                return;
            }
            int filterDoc = filterIter.NextDoc();
            int scorerDoc = scorer.Advance(filterDoc);

            collector.SetScorer(scorer);
            while (true)
            {
                if (scorerDoc == filterDoc)
                {
                    // Check if scorer has exhausted, only before collecting.
                    if (scorerDoc == DocIdSetIterator.NO_MORE_DOCS)
                    {
                        break;
                    }
                    collector.Collect(scorerDoc);
                    filterDoc = filterIter.NextDoc();
                    scorerDoc = scorer.Advance(filterDoc);
                }
                else if (scorerDoc > filterDoc)
                {
                    filterDoc = filterIter.Advance(scorerDoc);
                }
                else
                {
                    scorerDoc = scorer.Advance(filterDoc);
                }
            }
        }
    }
}
